package com.example.chainsave.upload.service;

import com.example.chainsave.common.exception.BizException;
import com.example.chainsave.common.model.Result;
import com.example.chainsave.common.util.HostHolder;
import com.example.chainsave.common.util.MailClient;
import com.example.chainsave.fabric.model.FileMetadata;
import com.example.chainsave.fabric.model.PartitionMetadata;
import com.example.chainsave.fabric.service.FileMetadataService;
import com.example.chainsave.fabric.service.PartitionMetadataService;
import com.example.chainsave.upload.dto.FileUploadRequestDTO;
import com.example.chainsave.upload.entity.User;
import com.example.chainsave.upload.enu.FileCheckMd5Status;
import com.example.chainsave.upload.util.FileMD5Util;
import com.example.chainsave.upload.util.FilePathUtil;
import com.example.chainsave.upload.util.FileSHA256Util;
import com.example.chainsave.upload.util.MerkleTreesUtil;
import common.BlockInfo;
import common.DNInfo;
import common.Utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import server.ClientImpl;

import java.io.File;
import java.io.FileNotFoundException;
import java.rmi.RemoteException;
import java.util.List;

@Service
@Slf4j
public class SaveAndFabricService {

    private ClientImpl client = new ClientImpl(Utils.localIp, 1099, true);

    @Autowired
    private FilePathUtil filePathUtil;

    @Autowired
    private HostHolder hostHolder;

    @Autowired
    private MailClient mailClient;

    @Autowired
    private TemplateEngine templateEngine;

    @Autowired
    private UserService userService;

    @Value("${storage.chunkSize}")
    private int storageChunkSize;

    public Result<FileMetadata> upload(FileUploadRequestDTO param) throws Exception {
        //调用存储接口上传本地文件
        log.info("这里是文件" + param.getName() + "调用存储接口upload开始上传的时间。");
        List<BlockInfo> blockInfos = client.put(filePathUtil.getBasePath(), param.getMd5(), filePathUtil.getFilePath(param));
        log.info("这里是文件" + param.getName() + "调用存储接口upload上传完成的时间。");
        //调用区块链接口CreateFileMetadata、CreatePartitionMetadata将元信息上链
        //分片IDs
        int partitionNum = blockInfos.size();
        int duplicateNum = blockInfos.get(0).getLocations().size();
        int[][] allPartitionIds = new int[partitionNum][duplicateNum];
        log.info("这里是文件" + param.getName() + "计算分片哈希的开始时间。");
        List<String> partitionHash = FileSHA256Util.getSHA256_local(filePathUtil.getFilePath(param),
                storageChunkSize * 1024 * 1024);
        log.info("这里是文件" + param.getName() + "计算分片哈希的结束时间。");
        for (int i = 0; i < partitionNum; i++) {
            BlockInfo blockInfo = blockInfos.get(i);
            String partitionSha256 = partitionHash.get(i);
            for (int j = 0; j < duplicateNum; j++) {
                DNInfo dnInfo = blockInfo.getLocations().get(j);
                String partitionAddress = blockInfo.getBlockNO() + ":" + dnInfo.getIp() + ":" + dnInfo.getPort();
                int type = 1;
                if (j == 0) {
//                    log.info("这里是文件" + param.getName() + "的分片" + i + "调用存储接口getBlock的开始时间。");
//                    byte[] partitionFileByte = client.getBlock(blockInfo.getBlockNO(),
//                            filePathUtil.getBasePath(), param.getMd5(), dnInfo.getIp(), dnInfo.getPort());
//                    log.info("这里是文件" + param.getName() + "的分片" + i + "调用存储接口getBlock的结束时间。");
//                    log.info("这里是文件" + param.getName() + "的分片" + i + "计算哈希的开始时间。");
//                    partitionSha256 = FileSHA256Util.getFileSHA256(partitionFileByte);
//                    log.info("这里是文件" + param.getName() + "的分片" + i + "计算哈希的结束时间。");
//                    partitionHash.add(partitionSha256);
                    type = 0;
                }
                System.out.println("partitionMetadata:" + partitionSha256 + "---" + partitionAddress + "---" + type);
                // 创建该文件所属的所有分片元数据
                PartitionMetadata partitionMetadata = PartitionMetadataService.createPartitionMetadata(
                        partitionSha256, partitionAddress, type).getData();
                allPartitionIds[i][j] = partitionMetadata.getPartitionMetadataId();
            }
        }
        MerkleTreesUtil merkleTree = new MerkleTreesUtil(partitionHash);
        merkleTree.merkle_tree();
        String fileHash = merkleTree.getMerkleRoot();
//        String md5="111";  //param.getMd5()
        FileMetadata fileMetadata = FileMetadata.builder().userId(param.getUserId()).fileHash(fileHash).fileMd5(param.getMd5())
                .fileName(param.getName()).fileDescription(param.getDescription()).partitionNum(partitionNum)
                .duplicateNum(duplicateNum).allPartitionIds(allPartitionIds).build();
        if (fileMetadata.getFileDescription() == null) fileMetadata.setFileDescription("");
        Result<FileMetadata> result = FileMetadataService.createFileMetadata(fileMetadata);
        if (result.getCode() != 200) {
            log.error("fabric error: 上传出错--" + result.toString());
        }
        return result;
    }

    public Result<Integer> checkFileMd5(FileUploadRequestDTO param) throws Exception {
        Result<List<FileMetadata>> result = FileMetadataService.queryFileMetadataByMd5(param.getMd5());
        Result<Integer> check = new Result<Integer>().setCode(result.getCode());
        List<FileMetadata> list = result.getData();
        if (list.size() != 0) {
            FileMetadata fileMetadata = list.get(0);
            //检查文件是否被篡改
            boolean isTamper = checkTamper(fileMetadata);
            if (isTamper) {
                check.setData(FileCheckMd5Status.FILE_NO_UPLOAD.getValue());
                //给所有被篡改该文件的用户更改文件元信息以及发送邮件
//                for (int i = 1; i < list.size(); i++) {
//                    FileMetadata param = list.get(i);
//                    param.setFileStatus(1);
//                    FileMetadataService.updateFileMetadata(param);
//                    //发送通知邮件
//                    int userId = param.getUserId();
//                    User user = userService.findUserById(userId);
//                    Context context = new Context();
//                    context.setVariable("email", user.getEmail());
//                    context.setVariable("fileName", param.getFileName());
//                    String content = templateEngine.process("/mail/tamper", context);
//                    mailClient.sendMail(user.getEmail(), "文件篡改提醒", content);
//                }
            } else {
                //增加文件元信息
                User user = hostHolder.getUser();
//                User user = new User();
//                user.setId(3);
                FileMetadata newFileMetadata = FileMetadata.builder().userId(user.getId()).fileHash(fileMetadata.getFileHash())
                        .fileMd5(param.getMd5()).fileName(param.getName()).fileDescription(param.getDescription())
                        .partitionNum(fileMetadata.getPartitionNum()).duplicateNum(fileMetadata.getDuplicateNum())
                        .allPartitionIds(fileMetadata.getAllPartitionIds()).build();
                if (newFileMetadata.getFileDescription() == null) newFileMetadata.setFileDescription("");
                Result<FileMetadata> look = FileMetadataService.createFileMetadata(newFileMetadata);
                check.setData(FileCheckMd5Status.FILE_UPLOADED.getValue());
            }
            //删除本地文件
            String filePath = filePathUtil.getFilePath(fileMetadata);
            File localFile = new File(filePath);
            localFile.delete();
        } else {
            check.setData(FileCheckMd5Status.FILE_NO_UPLOAD.getValue());
        }
        return check;
    }

    public Result<FileMetadata> download(int fileMetadataId) {
        Result<FileMetadata> result = new Result<>();
        try {
            FileMetadata fileMetadata = FileMetadataService.queryFileMetadataById(fileMetadataId).getData();
            boolean isTamper = checkTamper(fileMetadata);
//            boolean isTamper=true;
            if (isTamper) {
                result.setServerErrorMsgInfo("文件已被篡改，无法下载！");
            } else {
                result.setMessage("即将开始下载！").setData(fileMetadata);
            }
        } catch (Exception e) {
            log.error("fabric error: 查询文件元信息失败--" + e.getMessage(), e);
        }
        return result;
    }

    public boolean checkTamper(FileMetadata fileMetadata) {
        String localPath = filePathUtil.getFilePath(fileMetadata);
        boolean flag = false;
        boolean isTamper = false;
        try {
            log.info("这里是文件" + fileMetadata.getFileName() + "调用存储接口getFile开始下载的时间。");
            System.out.println("localPath:" + localPath);
            //调用存储接口获取文件
            flag = client.getFile(filePathUtil.getBasePath(), fileMetadata.getFileMd5(), localPath);
            log.info("这里是文件" + fileMetadata.getFileName() + "调用存储接口getFile下载完成的时间。");
            if (flag) {
                File file = new File(localPath);
                log.info("这里是文件" + fileMetadata.getFileName() + "计算md5值的开始时间。");
                String fileMd5 = FileMD5Util.getFileMD5(file);
                log.info("文件md5值计算结果为:" + fileMd5);
                log.info("这里是文件" + fileMetadata.getFileName() + "计算md5值的结束时间。");
                if (!fileMd5.equals(fileMetadata.getFileMd5())) {
                    isTamper = true;
                    fileMetadata.setFileStatus(1);
                    FileMetadataService.updateFileMetadata(fileMetadata);
                    //删除本地文件
                    file.delete();
                    //发送通知邮件
                    log.info("这里是文件" + fileMetadata.getFileName() + "的篡改邮件发送开始时间。");
                    int userId = fileMetadata.getUserId();
                    User user = userService.findUserById(userId);
                    Context context = new Context();
                    context.setVariable("email", user.getEmail());
                    context.setVariable("fileName", fileMetadata.getFileName());
                    String content = templateEngine.process("mail/tamper", context);
                    mailClient.sendMail(user.getEmail(), "文件篡改提醒", content);
                    log.info("这里是文件" + fileMetadata.getFileName() + "的篡改邮件发送结束时间。");
                }
            }
        } catch (RemoteException e) {
            log.error("save error: 下载到本地失败--" + e.getMessage(), e);
            throw new BizException(e.getMessage(), 406);
        } catch (FileNotFoundException e) {
            log.error("file error: 打开文件失败--" + e.getMessage(), e);
            throw new BizException(e.getMessage(), 406);
        } catch (Exception e) {
            log.error("fabric error: 更新文件元信息失败--" + e.getMessage(), e);
            throw new BizException(e.getMessage(), 406);
        }
        return isTamper;
    }

    //列出用户所有完整的文件
    public Result<List<FileMetadata>> listUserFile() throws Exception {
        User user = hostHolder.getUser();
        int fileStatus[] = {0};
        Result<List<FileMetadata>> result;
        if (user.getType() == 0) {
            result = FileMetadataService.queryFileMetadataByUserId(user.getId(), fileStatus);
        } else {
            result = FileMetadataService.queryAllFileMetadatas(fileStatus);
        }
        //处理数据
        List<FileMetadata> list = result.getData();
        for (int i = 0; i < list.size(); i++) {
            list.get(i).setFileHash(null);
            list.get(i).setFileMd5(null);
            list.get(i).setPartitionNum(0);
            list.get(i).setDuplicateNum(0);
            list.get(i).setAllPartitionIds(null);
        }
        return result;
    }

    //删除文件
    public Result<String> deleteFile(int fileMetadataId) throws Exception {
        Result<String> result = FileMetadataService.deleteFileMetadata(fileMetadataId);
        return result;
    }

}
